package com.gitee.ixtf.vertx.internal.converter

import cn.hutool.core.convert.ConverterRegistry
import com.fasterxml.jackson.databind.JsonNode
import com.gitee.ixtf.vertx.ParameterConverter
import com.gitee.ixtf.vertx.ParameterConverterProvider
import com.google.inject.Singleton
import graphql.schema.DataFetchingEnvironment
import io.smallrye.config.Converters
import io.vertx.core.buffer.Buffer
import io.vertx.core.eventbus.Message
import io.vertx.core.http.HttpServerRequest
import io.vertx.core.http.HttpServerResponse
import io.vertx.core.json.JsonArray
import io.vertx.core.json.JsonObject
import io.vertx.ext.web.FileUpload
import io.vertx.ext.web.RoutingContext
import jakarta.inject.Inject
import jakarta.ws.rs.FormParam
import jakarta.ws.rs.HeaderParam
import jakarta.ws.rs.PathParam
import jakarta.ws.rs.QueryParam
import java.lang.reflect.Parameter
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.security.Principal
import java.util.*
import kotlin.jvm.optionals.getOrNull
import org.dataloader.DataLoader
import org.eclipse.microprofile.config.spi.Converter
import org.eclipse.microprofile.graphql.Name
import org.eclipse.microprofile.graphql.Source

fun implicitConverter(clazz: Class<*>) =
    Converters.getImplicitConverter(clazz)
        ?: ConverterRegistry.getInstance().run { Converter { convert(clazz, it) } }

fun implicitConverter(parameter: Parameter) = implicitConverter(parameter.type)

@Suppress("UNCHECKED_CAST")
@Singleton
class BuildInParameterConverterProvider
@Inject
constructor(
    private val customListOpt: Optional<Collection<ParameterConverterProvider>>,
) : ParameterConverterProvider {
  @OptIn(ExperimentalStdlibApi::class)
  override fun <T> getConverter(
      parameter: Parameter,
      rawType: Class<T>,
      genericType: Type?,
      annotations: Array<Annotation>?
  ): ParameterConverter<T> {
    customListOpt.getOrNull()?.forEach {
      val converter = it.getConverter(parameter, rawType, genericType, annotations)
      if (converter != null) return converter
    }
    if (DataFetchingEnvironment::class.java.isAssignableFrom(rawType))
        return DataFetchingEnvironmentParameterConverter as ParameterConverter<T>
    if (RoutingContext::class.java.isAssignableFrom(rawType))
        return RoutingContextParameterConverter as ParameterConverter<T>
    if (HttpServerRequest::class.java.isAssignableFrom(rawType))
        return HttpServerRequestParameterConverter as ParameterConverter<T>
    if (HttpServerResponse::class.java.isAssignableFrom(rawType))
        return HttpServerResponseParameterConverter as ParameterConverter<T>
    if (Message::class.java.isAssignableFrom(rawType))
        return MessageParameterConverter as ParameterConverter<T>
    if (FileUpload::class.java.isAssignableFrom(rawType))
        return FileUploadParameterConverter as ParameterConverter<T>
    if (Principal::class.java.isAssignableFrom(rawType))
        return PrincipalParameterConverter as ParameterConverter<T>
    if (DataLoader::class.java.isAssignableFrom(rawType))
        return DataLoaderParamParameterConverter(parameter) as ParameterConverter<T>
    annotations?.forEach { annotation ->
      when (annotation) {
        is Source -> return SourceParameterConverter as ParameterConverter<T>
        is Name -> return NameParameterConverter(parameter) as ParameterConverter<T>
        is PathParam -> return PathParamParameterConverter(parameter) as ParameterConverter<T>
        is HeaderParam -> return HeaderParamParameterConverter(parameter) as ParameterConverter<T>
        is QueryParam -> return QueryParamParameterConverter(parameter) as ParameterConverter<T>
        is FormParam -> return FormParamParameterConverter(parameter) as ParameterConverter<T>
      }
    }
    (genericType as? ParameterizedType)?.let {
      if (FileUpload::class.java.isAssignableFrom(it.actualTypeArguments[0].javaClass))
          return FileUploadsParameterConverter as ParameterConverter<T>
    }
    if (String::class.java.isAssignableFrom(parameter.type))
        return Body2StringParameterConverter as ParameterConverter<T>
    if (ByteArray::class.java.isAssignableFrom(parameter.type))
        return Body2ByteArrayParameterConverter as ParameterConverter<T>
    if (JsonNode::class.java.isAssignableFrom(parameter.type))
        return Body2JsonNodeParameterConverter as ParameterConverter<T>
    if (Buffer::class.java.isAssignableFrom(parameter.type))
        return Body2BufferParameterConverter as ParameterConverter<T>
    if (JsonObject::class.java.isAssignableFrom(parameter.type))
        return Body2JsonObjectParameterConverter as ParameterConverter<T>
    if (JsonArray::class.java.isAssignableFrom(parameter.type))
        return Body2JsonArrayParameterConverter as ParameterConverter<T>
    if (Iterable::class.java.isAssignableFrom(parameter.type))
        return Body2IterableParameterConverter(parameter) as ParameterConverter<T>
    return Body2AnyParameterConverter(parameter) as ParameterConverter<T>
  }
}
